﻿#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>

//****************************************************************************************1 为什么要有动态内存分配

//int main()
//{
//	//已掌握的内存开辟的方式
//	int a = 10;
//	int arr[10];
//	//这两种方式并不灵活（大小固定，不能调整），因此就有了动态内存分配
//	return 0;
//}


//****************************************************************************************2 malloc和free

//**********************2.1 malloc
//**********************2.2 free
// void* malloc (size_t size);
//void free(void* ptr);

//int main()
//{
//	//申请10的整形的空间
//	int* p = (int*)malloc(10 * sizeof(int));
//
//	//判断返回的指针是否为NULL
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;//异常返回
//	}
//
//	使用动态内存
//	for (int i = 0; i < 10; i++)
//	{
//		*(p + i) = i + 1;
//	}
//
//	释放动态内存
//	free(p);
//	p = NULL;//避免p成为野指针
//	return 0;
//}

//注意：
//1.申请的动态内存需要手动申请，手动释放
//2.即使不手动释放，在程序结束后，系统会自动回收动态内存（不建议这样做）
//3.因此，malloc与free一般成对使用

//int main()
//{
//	int* p = NULL;
//	free(p);//若参数为NULL,free不做任何处理
//	return 0;
//}


/*
数组与动态内存的区别
1.动态内存的大小可以调整
2.开辟空间的位置不一样，动态内存在堆区，数组在栈区
3.
*/


//****************************************************************************************3 calloc和realloc

//**********************3.1 calloc
//void* calloc (size_t num, size_t size);

//int main()
//{
//	int* p = (int*)malloc(10 * sizeof(int));
//	int* p = (int*)(calloc(10, sizeof(int)));
//	if (p == NULL)
//	{
//		perror("calloc");//calloc与malloc不同的是，在返回地址之前把申请的空间的每个字节初始化为全0，而malloc则不会
//		return 1;//异常返回
//	}
//
//	//...
//
//	free(p);
//	p = NULL;
//	return 0;
//}

//**********************3.2 realloc
// void* realloc (void* ptr, size_t size);
// ptr 是要调整的内存的起始地址
// size 调整之后新⼤⼩
// 返回值为调整之后的内存起始位置
//调整动态开辟的内存

//int main()
//{
//	int* ptr = malloc(20);
//	if (ptr == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//
//	//由于可能调整失败会返回空指针，所以建议用一个新的指针接收返回值
//	int* tmp = realloc(ptr, 40);
//
//	if (tmp != NULL)
//	{
//		ptr = tmp;
//	}
//
//	//释放
//	free(ptr);
//	ptr = NULL;
//	
//	return 0;
//}


// realloc在调整内存空间的是存在两种情况：
// 情况1：原有空间之后有⾜够⼤的空间
// 情况2：原有空间之后没有⾜够⼤的空间

//调试观察

//int main()
//{
//	int* p = (int*)(calloc(10, sizeof(int)));
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//
//	for (int i = 0; i < 10; i++)
//	{
//		printf("%d ", p[i]);
//	}
//
//	//调整为20个整形的空间
//	int* tmp = (int*)realloc(p, 11 * sizeof(int));// 情况1
//	int* tmp = (int*)realloc(p, 20 * sizeof(int));// 情况2
//
//	if (tmp != NULL)
//	{
//		p = tmp;
//	}
//
//	//释放
//	free(p);
//	p = NULL;
//	return 0;
//}

//****************************************************************************************4 常⻅的动态内存的错误

//**********************4.1 对NULL指针的解引⽤操作

//void test()
//{
//	int* p = (int*)malloc(INT_MAX / 4);
//	*p = 20;//如果p的值是NULL，就会有问题
//	free(p);
//}

//**********************4.2 对动态开辟空间的越界访问

//void test()
//{
//	int i = 0;
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (NULL == p)
//	{
//		exit(EXIT_FAILURE);
//	}
//	for (i = 0; i <= 10; i++)
//	{
//		//以整形进行访问
//		*(p + i) = i;//当i是10的时候越界访问
//	}
//	free(p);
//}

//**********************4.3 对⾮动态开辟内存使⽤free释放

//void test()
//{
//	int a = 10;
//	int* p = &a;
//	free(p);//ok?---不行，p指向动态开辟的内存
//}

//**********************4.4 使⽤free释放⼀块动态开辟内存的⼀部分

//void test()
//{
//	int* p = (int*)malloc(100);
//	p++;
//	free(p);//p不再指向动态内存的起始位置
//}
 
//**********************4.5 对同⼀块动态内存多次释放

//void test()
//{
//	int* p = (int*)malloc(100);
//	free(p);
//	free(p);//重复释放
//}

//**********************4.6 动态开辟内存忘记释放（内存泄漏）

//void test()
//{
//	int* p = (int*)malloc(100);
//	if (NULL != p)
//	{
//		*p = 20;
//	}
//}
//int main()
//{
//	test();//函数执行完后，开辟的动态内存任然存在，但丢失了起始地址无法释放，导致内存泄漏
//	while (1);
//}



//****************************************************************************************5 动态内存经典笔试题分析

//**********************5.1 题⽬1

//void GetMemory(char* p)
//{
//	p = (char*)malloc(100);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(str);//str仍为空指针，倒是程序崩溃
//	strcpy(str, "hello world");
//	printf(str);//printf的使用没有问题
//}


//**********************5.2 题⽬2

//char* GetMemory(void)
//{
//	char p[] = "hello world";
//	return p;
//}
//void Test(void)
//{
//	char* str = NULL;
//
//	//返回栈空间地址的问题
//	str = GetMemory();//函数执行完后，局部数组的空间被系统回收，p指向的内存无使用权限,str成为野指针
//	printf(str);
//}

//int* test()
//{
//	int a = 10;
//	return &a;
//}
//
//int main()
//{
//	//int n = test();
//	int* p = test();
//	//函数栈帧的创建和销毁
//	printf("%d\n", p);//打印的是随机值----p没有这块空间的使用权限
//	return 0;
//}//


//**********************5.3 题⽬3

//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	strcpy(str, "hello");
//	printf(str);
//}
//可以打印，但存在问题：未释放内存，导致内存泄漏

//**********************5.4 题⽬4


//void Test(void)
//{
//	char* str = (char*)malloc(100);
//	strcpy(str, "hello");
//	free(str);
//	if (str != NULL)//这里的判断无效，str已成为野指针
//	{
//		strcpy(str, "world");//非法访问
//		printf(str);
//	}
//}

//正常写法

//void Test(void)
//{
//	char* str = (char*)malloc(100);
//	strcpy(str, "hello");
//	free(str);
//	str = NULL;//考察的是free完str是否置空
//	if (str != NULL)//这里的判断无效，str已成为野指针
//	{
//		strcpy(str, "world");
//		printf(str);
//	}
//}


//****************************************************************************************6 柔性数组

//C99 中，结构体中的最后⼀个元素允许是未知⼤⼩的数组，这就叫做『柔性数组』成员。

//第一种写法
//struct S1
//{
//	char c;
//	int a;
//	double d;
//	int arr[];//柔性数组成员
//};

//第二种写法
//struct S2
//{
//	char c;
//	int a;
//	double d;
//	int arr[0];//柔性数组成员
//};

//**********************6.1 柔性数组的特点

/*
	1.结构中的柔性数组成员前⾯必须⾄少有⼀个其他成员。
	2.sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
	3.包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤⼩，以适应柔性数组的预期⼤⼩。
*/

//**********************6.2 柔性数组的使⽤

//struct S
//{
//	int a;
//	int arr[];
//};
//
//int main()
//{
//	//printf("%zd\n", sizeof(struct S));//4----在计算时不包括柔性数组成员的大小
//	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));
//	if (ps == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//
//	ps->a = 100;
//	for (int i = 0; i < 20; i++)
//	{
//		ps->arr[i] = i + 1;//通过ps找到arr
//	}
//
//	//调整
//	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40 * sizeof(int));
//	if (ptr != NULL)
//	{
//		ps = ptr;
//		ptr = NULL;
//	}
//	else
//	{
//		return 1;
//	}
//
//	for (int i = 0; i < 40; i++)
//	{
//		printf("%x ", ps->arr[i]);
//	}
//
//	//释放，置空
//	free(ps);
//	ps = NULL;
//	return 0;
//}

//类比同效果的另一种写法

//struct S
//{
//	int a;
//	int* arr;
//};
//
//int main()
//{
//	struct S* ps = (struct S*)malloc(sizeof(struct S));
//	if (ps == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//
//	int* tmp = (int*)malloc(20 * sizeof(int));
//	if (tmp != NULL)
//	{
//		ps->arr = tmp;
//		tmp = NULL;
//	}
//	else
//	{
//		return 1;
//	}
//
//	ps->a = 100;
//	for (int i = 0; i < 20; i++)
//	{
//		ps->arr[i] = i + 1;
//	}
//
//	//调整
//
//	tmp = (int*)realloc(ps->arr, 40 * sizeof(int));
//	if (tmp != NULL)
//	{
//		ps->arr = tmp;
//		tmp = NULL;
//	}
//	else
//	{
//		return 1;
//	}
//
//	for (int i = 0; i < 40; i++)
//	{
//		printf("%x ", ps->arr[i]);
//	}
//
//	//逆序释放
//	free(ps->arr);
//	//ps->arr可以不用置空，在第二次释放时ps->arr也会被释放掉
//	free(ps);
//	ps = NULL;
//	return 0;
//}

//**********************6.3 柔性数组的优势

//第一中显然更好
//1.⽅便内存释放
//2.有益于提⾼访问速度，也有益于减少内存碎⽚



//****************************************************************************************7 总结C/C++中程序内存区域划分

/*
1 栈区：主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址
2 堆区：⼀般由程序员分配释放， 若程序员不释放，程序结束时可能由OS回收 。分配⽅式类似于链表
3 数据段（静态区）：（static）存放全局变量、静态数据。程序结束后由系统释放。

还有
内核空间：不可读写
内存映射段
代码段：存放函数体（类成员函数（C++）和全局函数）的⼆进制代码（不可修改）
*/
